/*
 * Copyright (c) 2025 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include <zephyr/autoconf.h>
#include <zephyr/bluetooth/addr.h>
#include <zephyr/bluetooth/audio/audio.h>
#include <zephyr/bluetooth/audio/bap.h>
#include <zephyr/bluetooth/audio/cap.h>
#include <zephyr/bluetooth/audio/csip.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/gap.h>
#include <zephyr/bluetooth/hci_types.h>
#include <zephyr/bluetooth/iso.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/util.h>
#include <zephyr/toolchain.h>

#include "bap_endpoint.h"
#include "cap_internal.h"
#include "csip_internal.h"

LOG_MODULE_REGISTER(bt_cap_handover, CONFIG_BT_CAP_HANDOVER_LOG_LEVEL);

static const struct bt_cap_handover_cb *cap_cb;

bool bt_cap_handover_is_handover_broadcast_source(
	const struct bt_cap_broadcast_source *cap_broadcast_source)
{

	const struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc();
	const struct bt_cap_handover_proc_param *proc_param = &active_proc->proc_param.handover;

	if (!bt_cap_common_handover_is_active()) {
		return false;
	}

	if (proc_param->is_unicast_to_broadcast) {
		return cap_broadcast_source == proc_param->unicast_to_broadcast.broadcast_source;
	}

	return cap_broadcast_source == proc_param->broadcast_to_unicast.broadcast_source;
}

struct cap_unicast_group_stream_lookup {
	struct bt_cap_stream *active_sink_streams[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT];
	struct bt_cap_stream *streams[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT];
	size_t active_sink_streams_cnt;
	size_t cnt;
};

static bool unicast_group_foreach_stream_cb(struct bt_cap_stream *cap_stream, void *user_data)
{
	struct cap_unicast_group_stream_lookup *data = user_data;
	const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream;

	__ASSERT_NO_MSG(data->cnt < ARRAY_SIZE(data->streams));
	__ASSERT_NO_MSG(data->active_sink_streams_cnt < ARRAY_SIZE(data->active_sink_streams));

	if (bap_stream->ep != NULL) {
		struct bt_bap_ep_info ep_info;
		int err;

		err = bt_bap_ep_get_info(bap_stream->ep, &ep_info);
		__ASSERT_NO_MSG(err == 0);

		/* Only consider sink streams for handover to broadcast */
		if (ep_info.state == BT_BAP_EP_STATE_STREAMING &&
		    ep_info.dir == BT_AUDIO_DIR_SINK) {
			data->active_sink_streams[data->active_sink_streams_cnt++] = cap_stream;
		}
	}

	data->streams[data->cnt++] = cap_stream;

	return false;
}

void bt_cap_handover_complete(void)
{
	struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc();
	struct bt_cap_handover_proc_param *proc_param = &active_proc->proc_param.handover;
	const bool is_unicast_to_broadcast = proc_param->is_unicast_to_broadcast;
	struct bt_conn *failed_conn = active_proc->failed_conn;
	struct bt_cap_broadcast_source *broadcast_source;
	struct bt_cap_unicast_group *unicast_group;
	const int err = active_proc->err;

	if (is_unicast_to_broadcast) {
		broadcast_source = proc_param->unicast_to_broadcast.broadcast_source;
		unicast_group = proc_param->unicast_to_broadcast.unicast_group;
	} else {
		broadcast_source = proc_param->broadcast_to_unicast.broadcast_source;
		unicast_group = proc_param->broadcast_to_unicast.unicast_group;
	}

	bt_cap_common_clear_active_proc();

	if (cap_cb != NULL) {
		if (is_unicast_to_broadcast) {
			if (cap_cb->unicast_to_broadcast_complete != NULL) {
				cap_cb->unicast_to_broadcast_complete(
					err, failed_conn, unicast_group, broadcast_source);
			}
		} else {
			if (cap_cb->broadcast_to_unicast_complete != NULL) {
				cap_cb->broadcast_to_unicast_complete(
					err, failed_conn, broadcast_source, unicast_group);
			}
		}
	}
}

void bt_cap_handover_unicast_proc_complete(void)
{
	struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc();
	const enum bt_cap_common_proc_type proc_type = active_proc->proc_type;

	if (proc_type == BT_CAP_COMMON_PROC_TYPE_STOP) {
		if (active_proc->err != 0) {
			bt_cap_handover_complete();
		} else {
			/* continue */
			bt_cap_handover_unicast_to_broadcast_setup_broadcast();
		}
	} else if (proc_type == BT_CAP_COMMON_PROC_TYPE_START) {
		bt_cap_handover_complete();
	} else {
		__ASSERT(false, "invalid proc_type %d", proc_type);
	}
}

void bt_cap_handover_broadcast_source_stopped(uint8_t reason)
{
	struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc();
	struct bt_cap_handover_proc_param *proc_param = &active_proc->proc_param.handover;

	if (proc_param->is_unicast_to_broadcast) {
		LOG_DBG("Unexpected broadcast source stop with reason 0x%02x", reason);

		__maybe_unused const int err = bt_cap_initiator_broadcast_audio_delete(
			proc_param->unicast_to_broadcast.broadcast_source);

		__ASSERT_NO_MSG(err == 0);
		proc_param->unicast_to_broadcast.broadcast_source = NULL;

		active_proc->err = reason;
		bt_cap_handover_complete();
	} else {
		if (reason == BT_HCI_ERR_LOCALHOST_TERM_CONN) {
			/* Successfully stopped the broadcast source */
			proc_param->broadcast_to_unicast.broadcast_stopped = true;
			if (proc_param->broadcast_to_unicast.reception_stopped) {
				bt_cap_handover_broadcast_audio_stopped();
			}
		} else {
			proc_param->unicast_to_broadcast.broadcast_source = NULL;
			active_proc->err = reason;
			bt_cap_handover_complete();
		}
	}
}

void bt_cap_handover_unicast_to_broadcast_reception_start(void)
{
	struct bt_cap_commander_broadcast_reception_start_param param = {0};
	struct bt_cap_initiator_broadcast_create_param *create_param;
	struct bt_cap_handover_proc_param *proc_param;
	struct bt_cap_common_proc *active_proc;
	struct bt_le_ext_adv_info adv_info;
	int err;

	active_proc = bt_cap_common_get_active_proc();
	proc_param = &active_proc->proc_param.handover;
	create_param = proc_param->unicast_to_broadcast.broadcast_create_param;
	param.type = proc_param->unicast_to_broadcast.type;
	param.param = proc_param->unicast_to_broadcast.reception_start_member_params;

	err = bt_le_ext_adv_get_info(proc_param->unicast_to_broadcast.ext_adv, &adv_info);
	__ASSERT_NO_MSG(err != -EINVAL);
	if (err != 0) {
		/* May happen if the advertising set was deleted while in this procedure */
		LOG_DBG("Failed to get adv info: %d", err);
		active_proc->err = err;
		active_proc->failed_conn = NULL;

		bt_cap_handover_complete();
		return;
	}

	if (adv_info.ext_adv_state != BT_LE_EXT_ADV_STATE_ENABLED) {
		/* Start advertising to get the actual adv addr */
		err = bt_le_ext_adv_start(proc_param->unicast_to_broadcast.ext_adv,
					  BT_LE_EXT_ADV_START_DEFAULT);
		if (err != 0) {
			LOG_DBG("Failed to start advertising set (err %d)\n", err);

			active_proc->err = err;
			active_proc->failed_conn = NULL;

			bt_cap_handover_complete();

			return;
		}

		/* Since bt_le_ext_adv_get_info returns the pointer to actual advertising address we
		 * do not need to call it again to get the address
		 */
	}
	/* else if we are already in the paused or active state and do not need to anything */

	ARRAY_FOR_EACH(proc_param->unicast_to_broadcast.reception_start_member_params, i) {
		struct bt_cap_commander_broadcast_reception_start_member_param *member_param =
			&proc_param->unicast_to_broadcast.reception_start_member_params[i];
		const struct bt_conn *member_conn = bt_cap_common_get_member_conn(
			proc_param->unicast_to_broadcast.type, &member_param->member);

		/* The member_conns are populated from index and upwards, thus once we
		 * reach a NULL pointer in the array, there are no more acceptors to send the
		 * reception start request to.
		 */
		if (member_conn == NULL) {
			break;
		}

		member_param->addr = bt_addr_le_any;
		member_param->adv_sid = adv_info.sid;
		member_param->pa_interval = proc_param->unicast_to_broadcast.pa_interval;
		member_param->broadcast_id = proc_param->unicast_to_broadcast.broadcast_id;
		bt_addr_le_copy(&member_param->addr, adv_info.addr);

		member_param->num_subgroups = create_param->subgroup_count;
		for (size_t j = 0U; j < member_param->num_subgroups; j++) {
			const struct bt_cap_initiator_broadcast_subgroup_param *subgroup_param =
				&create_param->subgroup_params[j];
			struct bt_bap_bass_subgroup *subgroup = &member_param->subgroups[j];

			subgroup->metadata_len = subgroup_param->codec_cfg->meta_len;
			(void)memcpy(subgroup->metadata, subgroup_param->codec_cfg->meta,
				     subgroup->metadata_len);

			/* The bis_sync value has been set up earlier in
			 * bt_cap_handover_unicast_to_broadcast while we still had the ACL
			 * references
			 */
			__ASSERT(subgroup->bis_sync != 0U, "BIS sync was not properly setup");
		}

		param.count++;
	}

	err = cap_commander_broadcast_reception_start(&param);
	if (err != 0) {
		LOG_DBG("Failed to start reception start: %d", err);

		bt_cap_handover_complete();
	}
}

void bt_cap_handover_unicast_to_broadcast_setup_broadcast(void)
{
	struct bt_cap_initiator_broadcast_create_param *broadcast_create_param;
	struct bt_cap_broadcast_source **broadcast_source;
	struct bt_cap_handover_proc_param *proc_param;
	struct bt_cap_common_proc *active_proc;
	struct bt_le_ext_adv *ext_adv;
	int err;

	active_proc = bt_cap_common_get_active_proc();
	proc_param = &active_proc->proc_param.handover;
	broadcast_create_param = proc_param->unicast_to_broadcast.broadcast_create_param;

	broadcast_source = &proc_param->unicast_to_broadcast.broadcast_source;

	err = bt_cap_unicast_group_delete(proc_param->unicast_to_broadcast.unicast_group);
	__ASSERT_NO_MSG(err == 0);
	proc_param->unicast_to_broadcast.unicast_group = NULL;

	err = bt_cap_initiator_broadcast_audio_create(broadcast_create_param, broadcast_source);
	if (err != 0) {
		LOG_DBG("Failed to create broadcast source: %d", err);
		active_proc->err = err;
		active_proc->failed_conn = NULL;

		bt_cap_handover_complete();

		return;
	}

	ext_adv = active_proc->proc_param.handover.unicast_to_broadcast.ext_adv;
	err = bt_cap_initiator_broadcast_audio_start(*broadcast_source, ext_adv);
	if (err != 0) {
		LOG_DBG("Failed to start broadcast source: %d", err);
		active_proc->err = err;
		active_proc->failed_conn = NULL;

		err = bt_cap_initiator_broadcast_audio_delete(*broadcast_source);
		__ASSERT_NO_MSG(err == 0);

		bt_cap_handover_complete();

		return;
	}
}

static bool valid_unicast_to_broadcast_stream_metadata_param(
	const struct bt_cap_initiator_broadcast_subgroup_param *subgroup_param,
	const struct bt_cap_stream *stream,
	const struct cap_unicast_group_stream_lookup *lookup_data)
{
	const uint8_t *broadcast_ccid_list;
	const uint8_t *unicast_ccid_list;
	int broadcast_ret;
	int unicast_ret;

	/* Compare existing unicast metadata with the subgroup param meteadata. It
	 * is mandatory that the CCID list and the context type remain the same
	 */
	if (stream->bap_stream.codec_cfg == subgroup_param->codec_cfg) {
		return true;
	}

	/* Verify CCID lists */
	unicast_ret = bt_audio_codec_cfg_meta_get_ccid_list(stream->bap_stream.codec_cfg,
							    &unicast_ccid_list);

	/* CCID list is not mandatory, so it is OK if it is missing, as long
	 * as it is missing for both unicast and broadcast
	 */

	if (unicast_ret < 0 && unicast_ret != -ENODATA) {
		return false;
	}

	broadcast_ret = bt_audio_codec_cfg_meta_get_ccid_list(subgroup_param->codec_cfg,
							      &broadcast_ccid_list);

	if (unicast_ret != broadcast_ret) {
		return false;
	}

	/* we only need to compare if the list exists and is non-empty */
	if (unicast_ret > 0 && !util_memeq(unicast_ccid_list, broadcast_ccid_list, unicast_ret)) {
		return false;
	}

	/* Verify streaming contexts (mandatory to be in the metadata )*/
	unicast_ret = bt_audio_codec_cfg_meta_get_stream_context(stream->bap_stream.codec_cfg);
	if (unicast_ret <= 0) { /* mandatory to have a streaming context */
		return false;
	}

	broadcast_ret = bt_audio_codec_cfg_meta_get_stream_context(subgroup_param->codec_cfg);
	if (unicast_ret != broadcast_ret) {
		return false;
	}

	return true;
}

static bool
valid_unicast_to_broadcast_metadata(const struct bt_cap_handover_unicast_to_broadcast_param *param,
				    const struct cap_unicast_group_stream_lookup *lookup_data)
{
	size_t unique_metadata_cnt = 0U;

	/* Count unique metadata from the unicast streams to verify the correct number of
	 * subgroups exist. Each unique set of metadata needs to be in its own group
	 */
	for (size_t i = 0U; i < lookup_data->active_sink_streams_cnt; i++) {
		const struct bt_bap_stream *bap_stream_i =
			&lookup_data->active_sink_streams[i]->bap_stream;
		const struct bt_audio_codec_cfg *codec_cfg_i = bap_stream_i->codec_cfg;
		bool unique_metadata = true;

		for (size_t j = 0U; j < i; j++) {
			const struct bt_bap_stream *bap_stream_j =
				&lookup_data->active_sink_streams[j]->bap_stream;
			const struct bt_audio_codec_cfg *codec_cfg_j = bap_stream_j->codec_cfg;

			if (codec_cfg_i == codec_cfg_j ||
			    util_eq(codec_cfg_i->meta, codec_cfg_i->meta_len, codec_cfg_j->meta,
				    codec_cfg_j->meta_len)) {
				unique_metadata = false;
				break;
			}
		}

		if (unique_metadata) {
			unique_metadata_cnt++;
		}
	}

	if (unique_metadata_cnt > CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT) {
		LOG_DBG("Cannot create broadcast source with %zu subgroups (max %d)",
			unique_metadata_cnt, CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT);

		return false;
	}

	if (unique_metadata_cnt > param->broadcast_create_param->subgroup_count) {
		LOG_DBG("Mismatch between unique metadata from unicast (%zu) and number of "
			"subgroups (%zu)",
			unique_metadata_cnt, param->broadcast_create_param->subgroup_count);

		return false;
	}

	return true;
}

static bool valid_unicast_to_broadcast_create_param(
	const struct bt_cap_handover_unicast_to_broadcast_param *param,
	const struct cap_unicast_group_stream_lookup *lookup_data)
{
	size_t total_broadcast_streams = 0U;

	for (size_t i = 0U; i < param->broadcast_create_param->subgroup_count; i++) {
		const struct bt_cap_initiator_broadcast_subgroup_param *subgroup_param =
			&param->broadcast_create_param->subgroup_params[i];

		for (size_t j = 0U; j < subgroup_param->stream_count; j++) {
			const struct bt_cap_stream *stream =
				subgroup_param->stream_params[j].stream;
			bool stream_is_handed_over = false;

			if (stream == NULL) {
				LOG_DBG("subgroup_param[%zu].stream_params[%zu].stream is NULL", i,
					j);
				return false;
			}

			if (stream->bap_stream.group != param->unicast_group->bap_unicast_group) {
				LOG_DBG("Stream %p is not part of the unicast group %p", stream,
					param->unicast_group->bap_unicast_group);
				return false;
			}

			for (size_t k = 0U; k < lookup_data->active_sink_streams_cnt; k++) {
				if (stream == lookup_data->active_sink_streams[k]) {
					stream_is_handed_over = true;
					break;
				}
			}

			if (!stream_is_handed_over) {
				LOG_DBG("Stream %p was not in unicast group %p", stream,
					param->unicast_group);
				return false;
			}

			if (!valid_unicast_to_broadcast_stream_metadata_param(
				    subgroup_param, stream, lookup_data)) {
				return false;
			}

			total_broadcast_streams++;
		}
	}

	if (total_broadcast_streams != lookup_data->active_sink_streams_cnt) {
		LOG_DBG("Not all unicast sink streams are being handed over to broadcast (%zu != "
			"%zu)",
			total_broadcast_streams, lookup_data->active_sink_streams_cnt);
		return false;
	}

	return true;
}

static bool
valid_unicast_to_broadcast_param(const struct bt_cap_handover_unicast_to_broadcast_param *param,
				 struct cap_unicast_group_stream_lookup *lookup_data)
{
	struct bt_le_ext_adv_info adv_info;
	int err;

	if (param == NULL) {
		LOG_DBG("param is NULL");
		return false;
	}

	if (param->unicast_group == NULL) {
		LOG_DBG("param->unicast_group is NULL");
		return false;
	}

	if (!bt_cap_initiator_broadcast_audio_start_valid_param(param->broadcast_create_param)) {
		LOG_DBG("param->broadcast_create_param is invalid");
		return false;
	}

	if (param->ext_adv == NULL) {
		LOG_DBG("param->ext_adv is NULL");
		return false;
	}

	err = bt_le_ext_adv_get_info(param->ext_adv, &adv_info);
	__ASSERT_NO_MSG(err == 0);

	if (adv_info.per_adv_state == BT_LE_PER_ADV_STATE_NONE) {
		LOG_DBG("Advertising set %p not configured for periodic advertising",
			param->ext_adv);
		return false;
	}

	if (param->broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
		LOG_DBG("param->broadcast_id is invalid: 0x%08X", param->broadcast_id);
		return false;
	}

	if (!IN_RANGE(param->pa_interval, BT_GAP_PER_ADV_MIN_INTERVAL,
		      BT_GAP_PER_ADV_MAX_INTERVAL)) {
		LOG_DBG("param->pa_interval is invalid: %u", param->pa_interval);
		return false;
	}

	err = bt_cap_unicast_group_foreach_stream(param->unicast_group,
						  unicast_group_foreach_stream_cb, lookup_data);
	__ASSERT_NO_MSG(err == 0);
	if (lookup_data->active_sink_streams_cnt == 0U) {
		LOG_DBG("param->unicast_group does not contain any active streams");

		return false;
	}

	if (!valid_unicast_to_broadcast_create_param(param, lookup_data)) {
		return false;
	}

	if (!valid_unicast_to_broadcast_metadata(param, lookup_data)) {
		return false;
	}

	return true;
}

int bt_cap_handover_unicast_to_broadcast(
	const struct bt_cap_handover_unicast_to_broadcast_param *param)
{
	struct cap_unicast_group_stream_lookup lookup_data = {0};
	struct bt_cap_unicast_audio_stop_param stop_param = {0};
	struct bt_cap_handover_proc_param *proc_param;
	struct bt_cap_common_proc *active_proc;
	uint8_t bis_index;
	int err;

	if (!valid_unicast_to_broadcast_param(param, &lookup_data)) {
		return -EINVAL;
	}

	if (bt_cap_common_test_and_set_proc_active()) {
		LOG_DBG("A CAP procedure is already in progress");

		return -EBUSY;
	}

	/* TBD: How do we prevent the unicast group from being modified or deleted while we are
	 * doing this procedure?
	 * TBD: How do we check for BASS?
	 */

	if (lookup_data.cnt == 0U) {
		LOG_DBG("param->unicast_group does not contain any streams");

		bt_cap_common_clear_active_proc();

		return -EINVAL;
	}

	active_proc = bt_cap_common_get_active_proc();
	proc_param = &active_proc->proc_param.handover;

	/* Populate an array of unique connection pointers to determine which acceptors to add the
	 * broadcast source to
	 */
	for (size_t i = 0U; i < lookup_data.active_sink_streams_cnt; i++) {
		const struct bt_cap_stream *stream = lookup_data.active_sink_streams[i];
		bool conn_added;

		/* Add stream's conn to conns if not already there */
		conn_added = false;
		ARRAY_FOR_EACH(proc_param->unicast_to_broadcast.reception_start_member_params, j) {
			struct bt_cap_commander_broadcast_reception_start_member_param
				*member_param = &proc_param->unicast_to_broadcast
							 .reception_start_member_params[j];
			const struct bt_conn *member_conn =
				bt_cap_common_get_member_conn(param->type, &member_param->member);

			if (member_conn == stream->bap_stream.conn) {
				conn_added = true;
				break;
			}

			if (member_conn == NULL) {
				if (param->type == BT_CAP_SET_TYPE_CSIP) {
					struct bt_cap_common_client *client =
						bt_cap_common_get_client_by_acl(
							stream->bap_stream.conn);

					member_param->member.csip =
						bt_csip_set_coordinator_csis_inst_by_handle(
							stream->bap_stream.conn,
							client->csis_start_handle);
				} else {
					member_param->member.member = stream->bap_stream.conn;
					conn_added = true;
					break;
				}
			}
		}

		if (!conn_added) {
			LOG_DBG("Stream %p connection could not be added. "
				"Some streams contain an invalid conn pointer",
				stream);

			bt_cap_common_clear_active_proc();

			return -EINVAL;
		}
	}

	/* We need to set up the expected BIS index fields for the reception start now, as once the
	 * unicast group has been stopped, the reference to the ACL connection from the stream will
	 * be lost
	 */

	bis_index = 1U; /* BIS indexes start from 1 */
	for (size_t i = 0U; i < param->broadcast_create_param->subgroup_count; i++) {
		const struct bt_cap_initiator_broadcast_subgroup_param *subgroup_param =
			&param->broadcast_create_param->subgroup_params[i];

		for (size_t j = 0U; j < subgroup_param->stream_count; j++) {
			const struct bt_cap_stream *stream =
				subgroup_param->stream_params[j].stream;

			ARRAY_FOR_EACH_PTR(
				proc_param->unicast_to_broadcast.reception_start_member_params,
				member_param) {
				const struct bt_conn *member_conn = bt_cap_common_get_member_conn(
					param->type, &member_param->member);

				/* Once we reach a NULL connection pointer, we've handled all
				 * acceptors from the unicast group
				 */
				if (member_conn == NULL) {
					break;
				}

				if (stream->bap_stream.conn == member_conn) {
					member_param->subgroups[i].bis_sync |=
						BT_ISO_BIS_INDEX_BIT(bis_index);
				}
			}

			bis_index++;
		}
	}

	/* Store the broadcast parameters for later */
	proc_param->unicast_to_broadcast.broadcast_create_param = param->broadcast_create_param;
	proc_param->unicast_to_broadcast.unicast_group = param->unicast_group;
	proc_param->unicast_to_broadcast.ext_adv = param->ext_adv;
	proc_param->unicast_to_broadcast.type = param->type;
	proc_param->unicast_to_broadcast.pa_interval = param->pa_interval;
	proc_param->unicast_to_broadcast.broadcast_id = param->broadcast_id;

	stop_param.type = param->type;
	stop_param.release = true;
	stop_param.streams = lookup_data.streams;
	stop_param.count = lookup_data.cnt;

	bt_cap_common_set_handover_active();
	proc_param->is_unicast_to_broadcast = true;

	__ASSERT_NO_MSG(bt_cap_initiator_valid_unicast_audio_stop_param(&stop_param));

	err = cap_initiator_unicast_audio_stop(&stop_param);
	if (err != 0) {
		LOG_DBG("Failed to stop unicast audio: %d", err);

		bt_cap_common_clear_active_proc();

		return -ENOEXEC;
	}

	return 0;
}

struct cap_broadcast_source_stream_lookup {
	struct bt_cap_stream *streams[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT];
	size_t cnt;
};

static bool broadcast_source_foreach_stream_cb(struct bt_cap_stream *cap_stream, void *user_data)
{
	struct cap_broadcast_source_stream_lookup *data = user_data;
	const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream;

	__ASSERT_NO_MSG(data->cnt < ARRAY_SIZE(data->streams));

	if (!bt_cap_initiator_stream_is_in_state(bap_stream, BT_BAP_EP_STATE_STREAMING)) {
		LOG_DBG("Stream %p is in invalid state %s", cap_stream,
			bt_bap_ep_state_str(bt_cap_initiator_stream_get_state(bap_stream)));
		return true;
	}

	data->streams[data->cnt++] = cap_stream;

	return false;
}

static bool valid_valid_broadcast_to_unicast_unicast_start_param(
	const struct bt_cap_handover_broadcast_to_unicast_param *param,
	const struct cap_broadcast_source_stream_lookup *lookup_data)
{

	for (size_t i = 0U; i < param->unicast_start_param->count; i++) {
		const struct bt_cap_unicast_audio_start_stream_param *stream_param =
			&param->unicast_start_param->stream_params[i];
		const struct bt_cap_stream *cap_stream = stream_param->stream;
		const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream;
		bool stream_is_handed_over = false;

		if (!bt_cap_initiator_stream_is_in_state(bap_stream, BT_BAP_EP_STATE_STREAMING)) {
			LOG_DBG("Stream %p is in invalid state %s", cap_stream,
				bt_bap_ep_state_str(bt_cap_initiator_stream_get_state(bap_stream)));
			return false;
		}

		if (bap_stream->group != param->broadcast_source->bap_broadcast) {
			LOG_DBG("Stream %p is not in the broadcast source", cap_stream);
			return false;
		}

		for (size_t j = 0U; j < lookup_data->cnt; j++) {
			if (cap_stream == lookup_data->streams[j]) {
				stream_is_handed_over = true;
				break;
			}
		}

		if (!stream_is_handed_over) {
			LOG_DBG("Stream %p was not in broadcast_source %p", cap_stream,
				param->broadcast_source);
			return false;
		}

		/* If we are doing reception stop, verify that all unicast streams being started are
		 * also stopping a broadcast reception
		 */
		if (param->reception_stop_param != NULL) {
			bool reception_stopped = false;

			for (size_t j = 0U; j < param->reception_stop_param->count; j++) {
				const struct bt_conn *member_conn = bt_cap_common_get_member_conn(
					param->reception_stop_param->type, &stream_param->member);
				const struct bt_conn *other_conn = bt_cap_common_get_member_conn(
					param->reception_stop_param->type,
					&param->reception_stop_param->param[j].member);

				if (member_conn == other_conn) {
					reception_stopped = true;
					break;
				}
			}

			if (!reception_stopped) {
				return false;
			}
		}
	}

	return true;
}

static bool
valid_broadcast_to_unicast_param(const struct bt_cap_handover_broadcast_to_unicast_param *param)
{
	struct cap_broadcast_source_stream_lookup lookup_data = {0};
	int err;

	if (param == NULL) {
		LOG_DBG("param is NULL");
		return false;
	}

	if (param->broadcast_source == NULL) {
		LOG_DBG("param->broadcast_source is NULL");
		return false;
	}

	if (!bt_cap_initiator_valid_unicast_group_param(param->unicast_group_param)) {
		LOG_DBG("param->unicast_group_param is invalid");
		return false;
	}

	if (!bt_cap_initiator_valid_unicast_audio_start_param(param->unicast_start_param)) {
		LOG_DBG("param->unicast_start_param is invalid");
		return false;
	}

	if (param->reception_stop_param == NULL) {
		if (param->broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) {
			LOG_DBG("param->broadcast_id is invalid: 0x%08X", param->broadcast_id);
			return false;
		}

		if (param->adv_sid > BT_GAP_SID_MAX) {
			LOG_DBG("param->adv_sid is larger than %d", BT_GAP_SID_MAX);
			return false;
		}

		if (param->adv_type != BT_ADDR_LE_PUBLIC && param->adv_type != BT_ADDR_LE_RANDOM) {
			LOG_DBG("Invalid address type %u", param->adv_type);
			return false;
		}
	} else {
		if (!bt_cap_commander_valid_broadcast_reception_stop_param(
			    param->reception_stop_param)) {
			LOG_DBG("param->reception_stop_param is invalid");
			return false;
		}

		if (param->unicast_start_param->type != param->reception_stop_param->type) {
			LOG_DBG("Mismatching types for param->unicast_start_param (%d) and "
				"param->reception_stop_param (%d)",
				param->unicast_start_param->type,
				param->reception_stop_param->type);
			return false;
		}
	}

	err = bt_cap_initiator_broadcast_foreach_stream(
		param->broadcast_source, broadcast_source_foreach_stream_cb, (void *)&lookup_data);
	if (err == -ECANCELED) {
		LOG_DBG("param->broadcast_source contain non-active streams");

		return false;
	}
	__ASSERT_NO_MSG(err == 0);

	if (lookup_data.cnt == 0U) {
		LOG_DBG("param->broadcast_source does not contain any streams");

		return false;
	}

	if (lookup_data.cnt != param->unicast_start_param->count) {
		LOG_DBG("Invalid unicast_start_param->count %u, expected %u",
			param->unicast_start_param->count, lookup_data.cnt);

		return false;
	}

	if (!valid_valid_broadcast_to_unicast_unicast_start_param(param, &lookup_data)) {
		LOG_DBG("param->unicast_start_param is invalid");

		return false;
	}

	return true;
}

int bt_cap_handover_broadcast_reception_stopped(void)
{
	struct bt_cap_handover_proc_param *proc_param;
	struct bt_cap_common_proc *active_proc;
	int err;

	active_proc = bt_cap_common_get_active_proc();
	proc_param = &active_proc->proc_param.handover;

	err = bt_cap_initiator_broadcast_audio_stop(
		proc_param->broadcast_to_unicast.broadcast_source);
	if (err != 0) {
		LOG_DBG("Failed to stop broadcast source: %d", err);
		active_proc->err = err;
	} /* Else wait for broadcast stopped */

	return err;
}

static bool bt_cap_handover_broadcast_to_unicast_all_stopped(void)
{
	struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc();
	bool all_stopped = true;

	ARRAY_FOR_EACH(
		active_proc->proc_param.handover.broadcast_to_unicast.pending_recv_state_conns, i) {
		if (active_proc->proc_param.handover.broadcast_to_unicast
			    .pending_recv_state_conns[i] != NULL) {
			all_stopped = false;
			break;
		}
	}

	return all_stopped;
}

void bt_cap_handover_receive_state_updated(const struct bt_conn *conn,
					   const struct bt_bap_scan_delegator_recv_state *state)
{
	struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc();
	struct bt_cap_handover_proc_param *proc_param = &active_proc->proc_param.handover;

	/* BAP 6.5.4 states that the Broadcast Assistant shall not initiate the Add
	 * Source operation if the operation would result in duplicate values for
	 * the combined Source_Address_Type, Source_Adv_SID, and Broadcast_ID fields
	 * of any Broadcast Receive State characteristic exposed by the Scan
	 * Delegator.
	 *
	 * We use that knowledge here to consider the triple {broadcast_id, sid,
	 * type} as being unique, which we will use to determine if a receive state
	 * notification with these values matches our broadcast source if we are not
	 * provided with a broadcast reception stop parameter
	 */

	if (!proc_param->is_unicast_to_broadcast &&
	    !proc_param->broadcast_to_unicast.reception_stopped &&
	    proc_param->broadcast_to_unicast.broadcast_id == state->broadcast_id &&
	    proc_param->broadcast_to_unicast.adv_sid == state->adv_sid &&
	    proc_param->broadcast_to_unicast.adv_type == state->addr.type) {

		ARRAY_FOR_EACH(proc_param->broadcast_to_unicast.pending_recv_state_conns, i) {
			if (proc_param->broadcast_to_unicast.pending_recv_state_conns[i] == conn) {
				proc_param->broadcast_to_unicast.pending_recv_state_conns[i] = NULL;
				break;
			}
		}

		if (bt_cap_handover_broadcast_to_unicast_all_stopped()) {
			proc_param->broadcast_to_unicast.reception_stopped = true;
			if (proc_param->broadcast_to_unicast.broadcast_stopped) {
				/* Delete source and start unicast */
				bt_cap_handover_broadcast_audio_stopped();
			}
		}
	}
}

void bt_cap_handover_broadcast_audio_stopped(void)
{
	struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc();
	struct bt_cap_handover_proc_param *proc_param = &active_proc->proc_param.handover;
	int err;

	/* This will be the case when we do broadcast to unicast handover and
	 * it successfully stops the broadcast
	 */

	err = bt_cap_initiator_broadcast_audio_delete(
		proc_param->broadcast_to_unicast.broadcast_source);
	if (err != 0) {
		LOG_DBG("Failed to delete broadcast source: %d", err);
		active_proc->err = err;

		bt_cap_handover_complete();

		return;
	}
	proc_param->broadcast_to_unicast.broadcast_source = NULL;

	err = bt_cap_unicast_group_create(proc_param->broadcast_to_unicast.unicast_group_param,
					  &proc_param->broadcast_to_unicast.unicast_group);
	if (err != 0) {
		LOG_DBG("Failed to create unicast group: %d", err);
		active_proc->err = err;

		bt_cap_handover_complete();

		return;
	}

	err = cap_initiator_unicast_audio_start(
		proc_param->broadcast_to_unicast.unicast_start_param);
	if (err != 0) {
		LOG_DBG("Failed to start unicast audio: %d", err);
		active_proc->err = err;

		bt_cap_handover_complete();

		return;
	}
}

int bt_cap_handover_broadcast_to_unicast(
	const struct bt_cap_handover_broadcast_to_unicast_param *param)
{
	struct bt_cap_handover_proc_param *proc_param;
	struct bt_cap_common_proc *active_proc;
	int err;

	/**
	 * This will perform the following operations
	 * 1) Check parameters
	 * 2) Broadcast Reception Stop (optional)
	 * 3) Broadcast Source Stop
	 * 4) Broadcast Source Delete
	 * 5) Unicast Group create
	 * 6) Unicast Audio Start
	 */

	if (!valid_broadcast_to_unicast_param(param)) {
		return -EINVAL;
	}

	if (bt_cap_common_test_and_set_proc_active()) {
		LOG_DBG("A CAP procedure is already in progress");

		return -EBUSY;
	}

	active_proc = bt_cap_common_get_active_proc();
	proc_param = &active_proc->proc_param.handover;
	proc_param->broadcast_to_unicast.unicast_group_param = param->unicast_group_param;
	proc_param->broadcast_to_unicast.unicast_start_param = param->unicast_start_param;
	proc_param->broadcast_to_unicast.broadcast_source = param->broadcast_source;
	proc_param->broadcast_to_unicast.broadcast_id = param->broadcast_id;
	proc_param->broadcast_to_unicast.adv_sid = param->adv_sid;
	proc_param->broadcast_to_unicast.adv_type = param->adv_type;

	bt_cap_common_set_handover_active();
	proc_param->is_unicast_to_broadcast = false;

	if (param->reception_stop_param == NULL) {
		/* Register the broadcast assistant callbacks to receive the BASS receive states */
		cap_commander_register_broadcast_assistant_callbacks();

		err = bt_cap_handover_broadcast_reception_stopped();
	} else {
		for (size_t i = 0U; i < param->reception_stop_param->count; i++) {
			proc_param->broadcast_to_unicast.pending_recv_state_conns[i] =
				bt_cap_common_get_member_conn(
					param->reception_stop_param->type,
					&param->reception_stop_param->param[i].member);
		}
		err = cap_commander_broadcast_reception_stop(param->reception_stop_param);
	}

	if (err != 0) {
		bt_cap_common_clear_active_proc();
	}

	return err;
}

int bt_cap_handover_register_cb(const struct bt_cap_handover_cb *cb)
{
	if (cb == NULL) {
		LOG_DBG("cb is NULL");
		return -EINVAL;
	}

	if (cap_cb != NULL) {
		LOG_DBG("callbacks already registered");
		return -EALREADY;
	}

	cap_cb = cb;

	return 0;
}

int bt_cap_handover_unregister_cb(const struct bt_cap_handover_cb *cb)
{
	if (cb == NULL) {
		LOG_DBG("cb is NULL");
		return -EINVAL;
	}

	if (cap_cb != cb) {
		LOG_DBG("cb is not registered");
		return -EINVAL;
	}

	cap_cb = NULL;

	return 0;
}
